using System;
using System.Collections;
using System.Diagnostics;
using System.Text;
using ActiproSoftware.SyntaxEditor.ParserGenerator;

namespace ActiproSoftware.SyntaxEditor.Addons.Simple {

	/// <summary>
	/// Represents a <c>Simple</c> lexical parser implementation.
	/// </summary>
	internal class SimpleLexicalParser : IMergableLexicalParser {

		private bool					caseSensitive;
		private Hashtable				keywords		= new Hashtable();
		
		/////////////////////////////////////////////////////////////////////////////////////////////////////
		// OBJECT
		/////////////////////////////////////////////////////////////////////////////////////////////////////

		/// <summary>
		/// Initializes a new instance of the <c>SimpleLexicalParser</c> class.
		/// </summary>
		/// <param name="caseSensitive">Whether the language is case sensitive.</param>
		public SimpleLexicalParser(bool caseSensitive) {
			// This is added for demo purposes... normally a language knows whether it is case sensitive or not
			this.caseSensitive = caseSensitive;

			// Initialize keywords
			for (int index = (int)SimpleTokenID.KeywordStart + 1; index < (int)SimpleTokenID.KeywordEnd; index++)
				keywords.Add(SimpleTokenID.GetTokenKey(index).ToLowerInvariant(), index);
		}
		
		/////////////////////////////////////////////////////////////////////////////////////////////////////
		// PUBLIC PROCEDURES
		/////////////////////////////////////////////////////////////////////////////////////////////////////
		
		/// <summary>
		/// Returns a single-character <see cref="ITokenLexicalParseData"/> representing the lexical parse data for the
		/// default token in the <see cref="ILexicalState"/> and seeks forward one position in the <see cref="ITextBufferReader"/>
		/// </summary>
		/// <param name="reader">An <see cref="ITextBufferReader"/> that is reading a text source.</param>
		/// <param name="lexicalState">The <see cref="ILexicalState"/> that specifies the current context.</param>
		/// <returns>The <see cref="ITokenLexicalParseData"/> for default text in the <see cref="ILexicalState"/>.</returns>
		public ITokenLexicalParseData GetLexicalStateDefaultTokenLexicalParseData(ITextBufferReader reader, ILexicalState lexicalState) {
			reader.Read();
			return new LexicalStateAndIDTokenLexicalParseData(lexicalState, (byte)lexicalState.DefaultTokenID);
		}

		/// <summary>
		/// Performs a lexical parse to return the next <see cref="ITokenLexicalParseData"/> 
		/// from a <see cref="ITextBufferReader"/> and seeks past it if there is a match.
		/// </summary>
		/// <param name="reader">An <see cref="ITextBufferReader"/> that is reading a text source.</param>
		/// <param name="lexicalState">The <see cref="ILexicalState"/> that specifies the current context.</param>
		/// <param name="lexicalParseData">Returns the next <see cref="ITokenLexicalParseData"/> from a <see cref="ITextBufferReader"/>.</param>
		/// <returns>A <see cref="MatchType"/> indicating the type of match that was made.</returns>
		public MatchType GetNextTokenLexicalParseData(ITextBufferReader reader, ILexicalState lexicalState, ref ITokenLexicalParseData lexicalParseData) {
			// Initialize
			int tokenID = SimpleTokenID.Invalid;

			// Get the next character
			char ch = reader.Read();

			// If the character is a letter or digit...
			if ((Char.IsLetter(ch) || (ch == '_'))) {
				// Parse the identifier
				tokenID = this.ParseIdentifier(reader, ch);
			}
			else if ((ch != '\n') && (Char.IsWhiteSpace(ch))) {
				while ((reader.Peek() != '\n') && (Char.IsWhiteSpace(reader.Peek()))) 
					reader.Read();
				tokenID = SimpleTokenID.Whitespace;
			}
			else {
				tokenID = SimpleTokenID.Invalid;
				switch (ch) {
					case ',':
						tokenID = SimpleTokenID.Comma;
						break;
					case '(':
						tokenID = SimpleTokenID.OpenParenthesis;
						break;
					case ')':
						tokenID = SimpleTokenID.CloseParenthesis;
						break;
					case ';':
						tokenID = SimpleTokenID.SemiColon;
						break;
					case '\n':
						// Line terminator
						tokenID = SimpleTokenID.LineTerminator;
						break;
					case '{':
						tokenID = SimpleTokenID.OpenCurlyBrace;
						break;
					case '}':
						tokenID = SimpleTokenID.CloseCurlyBrace;
						break;
					case '/':						
						tokenID = SimpleTokenID.Division;
						switch (reader.Peek()) {
							case '/':
								// Parse a single-line comment
								tokenID = this.ParseSingleLineComment(reader);
								break;
							case '*':
								// Parse a multi-line comment
								tokenID = this.ParseMultiLineComment(reader);
								break;
						}
						break;
					case '=':
						if (reader.Peek() == '=') {
							reader.Read();
							tokenID = SimpleTokenID.Equality;
						}
						else
							tokenID = SimpleTokenID.Assignment;
						break;
					case '!':
						if (reader.Peek() == '=') {
							reader.Read();
							tokenID = SimpleTokenID.Inequality;
						}
						break;
					case '+':
						tokenID = SimpleTokenID.Addition;
						break;
					case '-':
						tokenID = SimpleTokenID.Subtraction;
						break;
					case '*':
						tokenID = SimpleTokenID.Multiplication;
						break;
					default:
						if ((ch >= '0') && (ch <= '9')) {
							// Parse the number
							tokenID = this.ParseNumber(reader, ch);
						}
						break;
				}
			}

			if (tokenID != SimpleTokenID.Invalid) {
				lexicalParseData = new LexicalStateAndIDTokenLexicalParseData(lexicalState, (byte)tokenID);
				return MatchType.ExactMatch;
			}
			else {
				reader.ReadReverse();
				return MatchType.NoMatch;
			}
		}

		/// <summary>
		/// Parses an identifier.
		/// </summary>
		/// <param name="reader">An <see cref="ITextBufferReader"/> that is reading a text source.</param>
		/// <param name="ch">The first character of the identifier.</param>
		/// <returns>The ID of the token that was matched.</returns>
		protected virtual int ParseIdentifier(ITextBufferReader reader, char ch) {
			// Get the entire word
			int startOffset = reader.Offset - 1;
			while (!reader.IsAtEnd) {
				char ch2 = reader.Read();
				// NOTE: This could be improved by supporting \u escape sequences
				if ((!char.IsLetterOrDigit(ch2)) && (ch2 != '_')) {
					reader.ReadReverse();
					break;
				}
			}

			// Determine if the word is a keyword
			if (Char.IsLetter(ch)) {
				object value;
				if (caseSensitive)
					value = keywords[reader.GetSubstring(startOffset, reader.Offset - startOffset)];
				else
					value = keywords[reader.GetSubstring(startOffset, reader.Offset - startOffset).ToLowerInvariant()];

				if (value != null)
					return (int)value;
				else
					return SimpleTokenID.Identifier;
			}
			else
				return SimpleTokenID.Identifier;
		}

		/// <summary>
		/// Parses a multiple line comment.
		/// </summary>
		/// <param name="reader">An <see cref="ITextBufferReader"/> that is reading a text source.</param>
		/// <returns>The ID of the token that was matched.</returns>
		protected virtual int ParseMultiLineComment(ITextBufferReader reader) {
			reader.Read();
			while (reader.Offset < reader.Length) {
				if (reader.Peek() == '*') {
					if (reader.Offset + 1 < reader.Length) {
						if (reader.Peek(2) == '/') {
							reader.Read();
							reader.Read();
							break;
						}
					}
					else {
						reader.Read();
						break;
					}
				}
				reader.Read();
			}
			return SimpleTokenID.MultiLineComment;
		}
		
		/// <summary>
		/// Parses a number.
		/// </summary>
		/// <param name="reader">An <see cref="ITextBufferReader"/> that is reading a text source.</param>
		/// <param name="ch">The first character of the number.</param>
		/// <returns>The ID of the token that was matched.</returns>
		protected virtual int ParseNumber(ITextBufferReader reader, char ch) {
			while (Char.IsNumber(reader.Peek()))
				reader.Read();
			return SimpleTokenID.Number;
		}

		/// <summary>
		/// Parses a single line comment.
		/// </summary>
		/// <param name="reader">An <see cref="ITextBufferReader"/> that is reading a text source.</param>
		/// <returns>The ID of the token that was matched.</returns>
		protected virtual int ParseSingleLineComment(ITextBufferReader reader) {
			reader.Read();
			while ((!reader.IsAtEnd) && (reader.Peek() != '\n'))
				reader.Read();
			return SimpleTokenID.SingleLineComment;
		}
		

	}
}
